﻿using Microsoft.EntityFrameworkCore;
using Vote.Project001.Core.Database;
using Vote.Project001.Core.Database.Model;
using Vote.Project001.Core.Infrastructure;

namespace Vote.Project001.Core.Framework
{
    public class VoteManager: Singleton<VoteManager>
    {
        private class CategoryVoteData
        {
            public class VoteData
            {
                public int VoteCount { get; set; }
            }
            public int CategoryId { get; set; }
            public Dictionary<string, VoteData> UserVoteCountDictionary { get; private set; }

            public CategoryVoteData()
            {
                UserVoteCountDictionary = new Dictionary<string, VoteData>();
            }

        }

        private const int MaxVoteCountLimitPerCategory = 5;
        private const string ServiceStateKey = "ServiceState";

        private readonly Dictionary<int, CategoryVoteData> _categoryVoteData = new Dictionary<int, CategoryVoteData>();
        private readonly Dictionary<string, int> _heroVoteData = new Dictionary<string, int>();

        private readonly Dictionary<string, HashSet<string>> _userToHeroesDictionary =
            new Dictionary<string, HashSet<string>>();

        private readonly ServiceState _serviceState = ServiceState.Stopped; 

        private readonly object _lock = new object();

        protected override void OnInitialize()
        {
            using var dbContext = new Database.DatabaseContext();
            foreach (var vote in dbContext.Votes.AsNoTracking())
            {
                if(HeroManager.Instance.GetHero(vote.HeroId)==null)
                    continue;

                if (UserManager.Instance.GetUser(vote.UserId) == null)
                {
                    UserManager.Instance.AddUser(vote.UserId, "Unknown");
                }
                
                if (_heroVoteData.ContainsKey(vote.HeroId))
                {
                    _heroVoteData[vote.HeroId] = _heroVoteData[vote.HeroId] + 1;
                }
                else
                {
                    _heroVoteData.Add(vote.HeroId, 1);
                }

                CategoryVoteData categoryVoteData;
                if(_categoryVoteData.ContainsKey(vote.CategoryId))
                    categoryVoteData = _categoryVoteData[vote.CategoryId];
                else
                {
                    categoryVoteData = new CategoryVoteData() { CategoryId = vote.CategoryId };
                    _categoryVoteData.Add(vote.CategoryId, categoryVoteData);
                }

                if (categoryVoteData.UserVoteCountDictionary.ContainsKey(vote.UserId))
                {
                    categoryVoteData.UserVoteCountDictionary[vote.UserId].VoteCount = categoryVoteData.UserVoteCountDictionary[vote.UserId].VoteCount + 1;
                }
                else
                {
                    categoryVoteData.UserVoteCountDictionary.Add(vote.UserId,
                        new CategoryVoteData.VoteData() { VoteCount = 1 });
                }

                if (_userToHeroesDictionary.ContainsKey(vote.UserId))
                {
                    _userToHeroesDictionary[vote.UserId].Add(vote.HeroId);
                }
                else
                {
                    _userToHeroesDictionary.Add(vote.UserId, new HashSet<string> { vote.HeroId });
                }
            }
        }

        public void Reset()
        {
            lock (_lock)
            {
                _categoryVoteData.Clear();
                _heroVoteData.Clear();
                _userToHeroesDictionary.Clear();

                OnInitialize();
            }
        }

        public void Vote(string sessionId, string heroId)
        {
            var session = SessionManager.Instance.GetSession(sessionId);
            var hero = HeroManager.Instance.GetHero(heroId);

            if (session == null || hero == null)
                throw new InvalidOperationException("Session or Hero not exist.");

            if (_serviceState != ServiceState.Running)
                throw new WebApiException(WebApiFailureEnum.VoteDeactivated);

            using var dbContext = new Database.DatabaseContext();
            var transaction = dbContext.Database.BeginTransaction();
            dbContext.Votes.Add(new Database.Model.Vote()
            {
                UserId = session.UserId,
                HeroId = heroId,
                CategoryId = hero.CategoryId,
                VoteTime = DateTime.Now
            });
            dbContext.SaveChanges();

            lock (_lock)
            {
                if (_userToHeroesDictionary.ContainsKey(session.UserId))
                {
                    if (_userToHeroesDictionary[session.UserId].Contains(heroId))
                    {
                        transaction.Rollback();
                        throw new WebApiException(WebApiFailureEnum.VoteMoreThanOnce);
                    }
                    _userToHeroesDictionary[session.UserId].Add(heroId);
                }
                else
                {
                    _userToHeroesDictionary.Add(session.UserId, new HashSet<string>() { heroId });
                }

                if (_categoryVoteData.ContainsKey(hero.CategoryId))
                {
                    var category = _categoryVoteData[hero.CategoryId];
                    if (category.UserVoteCountDictionary.ContainsKey(session.UserId))
                    {
                        if (category.UserVoteCountDictionary[session.UserId].VoteCount >= MaxVoteCountLimitPerCategory)
                        {
                            // remove 
                            _userToHeroesDictionary[session.UserId].Remove(heroId);
                            transaction.Rollback();
                            throw new WebApiException(WebApiFailureEnum.VoteCountLimit, MaxVoteCountLimitPerCategory);
                        }

                        category.UserVoteCountDictionary[session.UserId].VoteCount =
                            category.UserVoteCountDictionary[session.UserId].VoteCount + 1;
                    }
                    else
                    {
                        category.UserVoteCountDictionary.Add(session.UserId, new CategoryVoteData.VoteData(){VoteCount = 1});
                    }
                }
                else
                {
                    var category = new CategoryVoteData() { CategoryId = hero.CategoryId };
                    _categoryVoteData.Add(hero.CategoryId, category);
                    category.UserVoteCountDictionary.Add(session.UserId,
                        new CategoryVoteData.VoteData() { VoteCount = 1 });
                }

                if (_heroVoteData.ContainsKey(heroId))
                {
                    _heroVoteData[heroId] = _heroVoteData[heroId] + 1;
                }
                else
                {
                    _heroVoteData.Add(heroId, 1);
                }

                
            }

            transaction.Commit();


        }

        public Dictionary<int,int>? GetAvailableVoteCount(string sessionId, int[] categoryIdList)
        {
            var session = SessionManager.Instance.GetSession(sessionId);
            if (session == null) return null;

            lock (_lock)
            {
                var result = new Dictionary<int, int>();
                foreach (var i in categoryIdList)
                {
                    if (!_categoryVoteData.ContainsKey(i))
                    {
                        result.Add(i, MaxVoteCountLimitPerCategory);
                    }
                    else
                    {
                        var category = _categoryVoteData[i];
                        if (category.UserVoteCountDictionary.ContainsKey(session.UserId))
                        {
                            var voteCount = category.UserVoteCountDictionary[session.UserId];
                            result.Add(i,
                                voteCount.VoteCount > MaxVoteCountLimitPerCategory
                                    ? 0
                                    : MaxVoteCountLimitPerCategory - voteCount.VoteCount);
                        }
                        else
                        {
                            result.Add(i, MaxVoteCountLimitPerCategory);
                        }
                    }
                }

                return result;
            }
        }

        public HashSet<string> GetUserVoteList(string userId)
        {
            lock (_lock)
            {
                return _userToHeroesDictionary.ContainsKey(userId) ? _userToHeroesDictionary[userId] : new HashSet<string>();
            }
        }

        public Dictionary<string, int> GetVoteData()
        {
            lock (_lock)
            {
                var result = new Dictionary<string, int>();
                foreach (var (key, value) in _heroVoteData)
                {
                    result.Add(key, value);
                }
                return result;
            }
        }

        public int GetHeroVoteCount(string heroId)
        {
            lock (_lock)
            {
                if (_heroVoteData.ContainsKey(heroId))
                {
                    return _heroVoteData[heroId];
                }
                else
                {
                    return 0;
                }
            }
        }
    }
}
